/*
 * Copyright 2006 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.gwt.user.client.ui;

import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Element;

/**
 * A panel that lays its child widgets out "docked" at its outer edges, and
 * allows its last widget to take up the remaining space in its center.
 * 
 * <p>
 * <img class='gallery' src='DockPanel.png'/>
 * </p>
 */
public class DockPanel extends CellPanel implements HasAlignment {

  /**
   * DockPanel layout constant, used in {@link DockPanel#add(Widget, DockLayoutConstant)}.
   */
  public static class DockLayoutConstant {
    private DockLayoutConstant() {
    }
  }

  private static class LayoutData {

    public DockLayoutConstant direction;
    public String hAlign = "left";
    public String height = "";
    public Element td;
    public String vAlign = "top";
    public String width = "";

    public LayoutData(DockLayoutConstant dir) {
      direction = dir;
    }
  }

  private static class TmpRow {
    public int center;
    public Element tr;
  }

  /**
   * Specifies that a widget be added at the center of the dock.
   */
  public static final DockLayoutConstant CENTER = new DockLayoutConstant();

  /**
   * Specifies that a widget be added at the east edge of the dock.
   */
  public static final DockLayoutConstant EAST = new DockLayoutConstant();

  /**
   * Specifies that a widget be added at the north edge of the dock.
   */
  public static final DockLayoutConstant NORTH = new DockLayoutConstant();

  /**
   * Specifies that a widget be added at the south edge of the dock.
   */
  public static final DockLayoutConstant SOUTH = new DockLayoutConstant();

  /**
   * Specifies that a widget be added at the west edge of the dock.
   */
  public static final DockLayoutConstant WEST = new DockLayoutConstant();

  private Element bodyElem;
  private boolean dirty = true;
  private HorizontalAlignmentConstant horzAlign = ALIGN_LEFT;
  private VerticalAlignmentConstant vertAlign = ALIGN_TOP;

  /**
   * Creates an empty dock panel.
   */
  public DockPanel() {
    bodyElem = DOM.createTBody();

    DOM.appendChild(getElement(), bodyElem);
    DOM.setIntAttribute(getElement(), "cellSpacing", 0);
    DOM.setIntAttribute(getElement(), "cellPadding", 0);

    setElement(getElement());
  }

  public boolean add(Widget w) {
    return add(w, NORTH);
  }

  /**
   * Adds a widget to the specified edge of the dock.
   * 
   * @param widget the widget to be added
   * @param direction the widget's direction in the dock
   * @return <code>true</code> if successful (no widgets may be added after
   *         the {@link #CENTER} widget)
   */
  public boolean add(Widget widget, DockLayoutConstant direction) {
    if (getWidgetCount() > 0) {
      Widget last = getWidget(getWidgetCount() - 1);
      if (((LayoutData) last.getLayoutData()).direction == CENTER)
        return false;
    }

    super.add(widget);
    widget.setLayoutData(new LayoutData(direction));
    setCellHorizontalAlignment(widget, horzAlign);
    setCellVerticalAlignment(widget, vertAlign);
    deferRealize();
    return true;
  }

  public HorizontalAlignmentConstant getHorizontalAlignment() {
    return horzAlign;
  }

  public VerticalAlignmentConstant getVerticalAlignment() {
    return vertAlign;
  }

  public boolean remove(Widget w) {
    boolean ret = super.remove(w);
    if (ret)
      deferRealize();
    return ret;
  }

  public void setHorizontalAlignment(HorizontalAlignmentConstant align) {
    horzAlign = align;
  }

  public void setVerticalAlignment(VerticalAlignmentConstant align) {
    vertAlign = align;
  }

  public void setCellHeight(Widget w, String height) {
    LayoutData data = (LayoutData) w.getLayoutData();
    data.height = height;
    if (data.td != null)
      DOM.setStyleAttribute(data.td, "height", data.height);
  }

  public void setCellHorizontalAlignment(Widget w, HorizontalAlignmentConstant align) {
    LayoutData data = (LayoutData) w.getLayoutData();
    data.hAlign = align.getTextAlignString();
    if (data.td != null)
      DOM.setAttribute(data.td, "align", data.hAlign);
  }

  public void setCellVerticalAlignment(Widget w, VerticalAlignmentConstant align) {
    LayoutData data = (LayoutData) w.getLayoutData();
    data.vAlign = align.getVerticalAlignString();
    if (data.td != null)
      DOM.setStyleAttribute(data.td, "verticalAlign", data.vAlign);
  }

  public void setCellWidth(Widget w, String width) {
    LayoutData data = (LayoutData) w.getLayoutData();
    data.width = width;
    if (data.td != null)
      DOM.setStyleAttribute(data.td, "width", data.width);
  }

  protected void onLoad() {
    realizeTable();
  }

  private void deferRealize() {
    if (dirty)
      return;
    dirty = true;

    DeferredCommand.add(new Command() {
      public void execute() {
        realizeTable();
      }
    });
  }

  private void realizeTable() {
    if (!dirty)
      return;
    dirty = false;

    while (DOM.getChildCount(bodyElem) > 0)
      DOM.removeChild(bodyElem, DOM.getChild(bodyElem, 0));

    int rowCount = 1, colCount = 1;
    for (int i = 0; i < getWidgetCount(); ++i) {
      DockLayoutConstant dir = ((LayoutData) getWidget(i).getLayoutData()).direction;
      if ((dir == NORTH) || (dir == SOUTH))
        ++rowCount;
      else if ((dir == EAST) || (dir == WEST))
        ++colCount;
    }

    TmpRow[] rows = new TmpRow[rowCount];
    for (int i = 0; i < rowCount; ++i) {
      rows[i] = new TmpRow();
      rows[i].tr = DOM.createTR();
      DOM.appendChild(bodyElem, rows[i].tr);
    }

    int westCol = 0, eastCol = colCount - 1;
    int northRow = 0, southRow = rowCount - 1;
    for (int i = 0; i < getWidgetCount(); ++i) {
      Widget child = getWidget(i);
      LayoutData layout = (LayoutData) child.getLayoutData();

      Element td = DOM.createTD();
      DOM.appendChild(td, child.getElement());
      layout.td = td;
      DOM.setAttribute(layout.td, "align", layout.hAlign);
      DOM.setStyleAttribute(layout.td, "verticalAlign", layout.vAlign);
      DOM.setAttribute(layout.td, "width", layout.width);
      DOM.setAttribute(layout.td, "height", layout.height);

      if (layout.direction == NORTH) {
        DOM.insertChild(rows[northRow].tr, td, rows[northRow].center);
        DOM.setIntAttribute(td, "colSpan", eastCol - westCol + 1);
        ++northRow;
      } else if (layout.direction == SOUTH) {
        DOM.insertChild(rows[southRow].tr, td, rows[southRow].center);
        DOM.setIntAttribute(td, "colSpan", eastCol - westCol + 1);
        --southRow;
      } else if (layout.direction == WEST) {
        TmpRow row = rows[northRow];
        DOM.insertChild(row.tr, td, row.center++);
        DOM.setIntAttribute(td, "rowSpan", southRow - northRow + 1);
        ++westCol;
      } else if (layout.direction == EAST) {
        TmpRow row = rows[northRow];
        DOM.insertChild(row.tr, td, row.center);
        DOM.setIntAttribute(td, "rowSpan", southRow - northRow + 1);
        --eastCol;
      } else if (layout.direction == CENTER) {
        TmpRow row = rows[northRow];
        DOM.insertChild(row.tr, td, row.center);
      }
    }
  }
}